Conversation
…y, wizard step 2 Four issues from the 2026-05-02 staging QA pass, kept tight at 3 source files: - sw.js: precache app shell on install (`/`, `/index.html`, manifest, icons) and serve cached `/index.html` for navigation requests that fail the network. Cold offline boots now render the SPA shell instead of the browser's default offline page. Cache version bumped v1 → v2 so the existing activate-cleanup evicts the old cache. - SettingsPage.tsx: replace 6× `text-[#768081]` and 1× `text-[#a3adae]` (3.1:1 / 4.0:1 against their backgrounds — fail WCAG AA) with `text-surface-600` / `text-surface-500` (~7.4:1 / ~5.8:1 — pass AA). Outer `<div>` → `<main id="main-content">` so the skip-link target works on this non-shell route too. - Step2WhoWhere.tsx: the bottom action region was conditionally rendered on `locationMethod !== null`, so before users picked GPS/Manual they saw no button and no hint — read as silent failure. Always render the region; show a `role="status"` hint until a method is chosen, then render the existing Button. Gate: lint + typecheck clean, 319/319 vitest pass, `pnpm build` emits all five precache URLs. The broader `<main>` landmark sweep across other non-shell screens (Register, Login, NotFound, Goodbye, Onboarding, the wizard root, Lookup, Tracking, Receipt, IncidentDetail) is booked as a follow-up — out of scope here per the 3-file budget. Manual verification still required after staging redeploy: offline reload, Lighthouse a11y on /settings, Step 2 hint visibility. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…p, login↔register phone preservation Five real bugs from the round-2 staging QA pass, scoped to the citizen-pwa. The other four findings break out as: Firebase Phone Auth disabled (Console config, not code), report-tracking 404 fixed in code by `90f29ef` and waiting on a staging redeploy, Register architecture (`linkWithPhoneNumber` requires `currentUser` but no `signInAnonymously` exists upstream — design decision, not a small fix), and Step 2 back-from-Step-3 field loss (deferred — needs `initialValues` props on `Step2WhoWhere`). - services/wizard-snapshot.ts (NEW) + .test.ts (8 tests): single-snapshot store on its own localforage instance with a 24 h TTL. Distinct from `draft-store` because in-progress wizard state has different semantics — no publicRef/secretHash yet. Best-effort persistence: storage errors fall through silently rather than block step transitions. - SubmitReportForm/index.tsx: load snapshot on mount, save on every step/formData change (gated on `hasLoadedSnapshot` so the initial empty state can't clobber a fresh resume), clear on submit success and on Step-1 back-to-home. Photo files are NOT persisted (per-keystroke blob writes would bloat the snapshot); user re-attaches if needed. - Step1Evidence.tsx: default `reportType` to `''` instead of seeded `'flood'`, accept `initialReportType` so back-from-Step-2 keeps the pick. `handleNext` validates and shows inline `role="alert"` error when the type is missing — closes the silent path where the "Skip photo for now" button bypassed the disabled main button. - LoginPage.tsx + RegisterPage.tsx: both seed `phone` from `sessionStorage["bantayog.last-phone"]` via a `useState` lazy initializer, write back on every keystroke. RegisterPage's existing `?resume=registration` effect still wins when `currentUser.phoneNumber` is present. - RegisterPage.tsx: phone input gains `id="register-phone"`, `name="phone"`, `autoComplete="tel"`, and a real `<label htmlFor>` — closes the a11y warning. Gate: lint + typecheck clean, 327/327 vitest pass (54 files, +8 new snapshot tests), `pnpm build` clean (SubmitReportForm chunk +1 KB). Manual verification still required after staging redeploy: see docs/progress.md for the four-item checklist. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# Conflicts: # docs/learnings.md # docs/progress.md
Reviewer's GuideThis PR implements two QA rounds for the citizen PWA, focusing on offline shell behavior, a11y improvements, auth phone-number preservation across Login/Register, and wizard resumability via a new snapshot service, plus validation fixes for the report wizard. Sequence diagram for wizard resumability using wizardSnapshotsequenceDiagram
actor User
participant Browser
participant WizardContainer
participant wizardSnapshot
User->>Browser: Open /report or refresh
Browser->>WizardContainer: Mount component
WizardContainer->>wizardSnapshot: load()
wizardSnapshot-->>WizardContainer: snapshot or null
alt snapshot exists and fresh
WizardContainer->>WizardContainer: set step from snapshot.step
WizardContainer->>WizardContainer: set formData.step1 (photoFile null)
WizardContainer->>WizardContainer: set formData.step2
else no snapshot
WizardContainer->>WizardContainer: keep default step=1, empty formData
end
WizardContainer->>WizardContainer: set hasLoadedSnapshot = true
WizardContainer-->>User: Render wizard (Step1/2/3)
loop On every step or formData change
WizardContainer->>wizardSnapshot: save({ step, step1: { reportType }, step2 })
wizardSnapshot->>wizardSnapshot: stamp updatedAt
wizardSnapshot-->>WizardContainer: persist best-effort
end
alt User taps Back on Step1
WizardContainer->>wizardSnapshot: clear()
wizardSnapshot-->>WizardContainer: snapshot removed (best-effort)
WizardContainer->>Browser: navigate('/')
else User completes submission
WizardContainer->>wizardSnapshot: clear()
wizardSnapshot-->>WizardContainer: snapshot removed (best-effort)
WizardContainer->>Browser: navigate('/reports/{publicRef}')
end
Sequence diagram for phone preservation between LoginPage and RegisterPagesequenceDiagram
actor User
participant LoginPage
participant sessionStorage
participant RegisterPage
User->>LoginPage: Visit /login
LoginPage->>sessionStorage: getItem(bantayog.last-phone)
alt value exists
sessionStorage-->>LoginPage: "+639XXXXXXXXX"
LoginPage->>LoginPage: phone state = stored value
else no value or error
sessionStorage-->>LoginPage: null or throw
LoginPage->>LoginPage: phone state = "+63"
end
LoginPage-->>User: Phone input prefilled
User->>LoginPage: Type phone number
LoginPage->>LoginPage: setPhone(next)
LoginPage->>sessionStorage: setItem(bantayog.last-phone, next) (best-effort)
User->>LoginPage: Navigate to /register
User->>RegisterPage: Visit /register
RegisterPage->>RegisterPage: Determine isResume from searchParams
alt isResume
RegisterPage->>RegisterPage: phone state = "+63"
else not resume
RegisterPage->>sessionStorage: getItem(bantayog.last-phone)
alt value exists
sessionStorage-->>RegisterPage: "+639XXXXXXXXX"
RegisterPage->>RegisterPage: phone state = stored value
else no value or error
sessionStorage-->>RegisterPage: null or throw
RegisterPage->>RegisterPage: phone state = "+63"
end
end
RegisterPage-->>User: Phone input shows preserved value
User->>RegisterPage: Edit phone (optional)
RegisterPage->>RegisterPage: setPhone(next)
RegisterPage->>sessionStorage: setItem(bantayog.last-phone, next) (best-effort)
Class diagram for wizardSnapshot service and SubmitReportForm componentsclassDiagram
class SerializableStep1 {
+string reportType
}
class SerializableStep2 {
+number location_lat
+number location_lng
+string reporterName
+string reporterMsisdn
+number patientCount
+string locationMethod // 'gps' | 'manual'
+string municipalityId
+string municipalityLabel
+string barangayId
+string nearestLandmark
}
class WizardSnapshot {
+number step // 1 | 2 | 3
+SerializableStep1 step1
+SerializableStep2 step2
+number updatedAt
}
class WizardSnapshotStore {
+load() WizardSnapshot
+save(step, step1, step2) void
+clear() void
}
class WizardContainer {
-boolean hasLoadedSnapshot
-number step // 1 | 2 | 3
-FormData formData
-Draft draft
-string secret
-boolean isCreatingDraft
-string draftError
+handleStep1Next(step1Data)
+handleStep2Next(step2Data)
+handleStep2Back()
+handleStep1Back()
}
class Step1EvidenceProps {
+function onNext(data)
+function onBack()
+boolean isSubmitting
+string initialReportType
}
class Step1Evidence {
-string reportType
-string reportTypeError
-File photoFile
-string photoError
+handleNext()
}
class Step2WhoWhere {
-string locationMethod // gps | manual | null
}
class LoginPage {
-string phone
+setPhone(next)
}
class RegisterPage {
-string phone
+setPhone(next)
}
WizardSnapshotStore --> WizardSnapshot : uses
WizardContainer --> WizardSnapshotStore : load/save/clear
WizardContainer --> Step1Evidence : renders
WizardContainer --> Step2WhoWhere : renders
Step1Evidence ..> Step1EvidenceProps : implements
WizardSnapshot --> SerializableStep1 : has
WizardSnapshot --> SerializableStep2 : has
LoginPage --> SessionStorageHelper : read/write last phone
RegisterPage --> SessionStorageHelper : read/write last phone
class SessionStorageHelper {
+getLastPhone() string
+setLastPhone(phone) void
}
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThis PR adds a localforage-backed wizard snapshot service with TTL and tests, wires resume/save/clear logic into the SubmitReportForm, validates Step 1 incident type, persists last-entered phone via sessionStorage, updates the service worker to precache the app shell and serve cached /index.html for navigation fallbacks, and applies minor UI/accessibility/style tweaks and docs. ChangesCitizen PWA: Wizard Resume, Phone Persistence, SW, and UI tweaks
Sequence DiagramssequenceDiagram
actor User
participant Browser
participant WizardComponent as SubmitReportForm
participant SnapshotService as wizardSnapshot
participant LocalForage
User->>Browser: Open wizard
Browser->>WizardComponent: mount
WizardComponent->>SnapshotService: load()
SnapshotService->>LocalForage: getItem(snapshotKey)
LocalForage-->>SnapshotService: snapshot or null
SnapshotService-->>WizardComponent: Snapshot | null
WizardComponent->>WizardComponent: rehydrate step & formData
WizardComponent->>SnapshotService: save({step, step1, step2})
SnapshotService->>LocalForage: setItem(snapshotKey, payload+updatedAt)
LocalForage-->>SnapshotService: success
User->>WizardComponent: abandon or submit
WizardComponent->>SnapshotService: clear()
SnapshotService->>LocalForage: removeItem(snapshotKey)
sequenceDiagram
actor User
participant Browser
participant ServiceWorker as SW
participant Cache
participant Network
User->>Browser: Request SPA route (navigate)
Browser->>SW: fetch(request)
SW->>Network: fetch(request)
Network-->>SW: network error
SW->>Cache: match("/index.html")
Cache-->>SW: cached /index.html
SW-->>Browser: respond with /index.html
Browser->>Browser: bootstrap SPA and render route
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~40 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: Turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 👉 Get your free trial and get 200 agent minutes per Slack user (a $50 value). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 11 minutes and 14 seconds.Comment |
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
WizardContainer, thehasLoadedSnapshotgate currently returns an emptydivwitharia-hidden="true"; consider rendering a minimal loading/skeleton state with an accessible status instead so users aren’t presented with a blank screen while the snapshot loads. - The
sessionStorage["bantayog.last-phone"]key and access pattern are duplicated inLoginPageandRegisterPage; extracting this into a small shared helper (with the try/catch and default+63) would reduce drift and centralize the persistence logic.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `WizardContainer`, the `hasLoadedSnapshot` gate currently returns an empty `div` with `aria-hidden="true"`; consider rendering a minimal loading/skeleton state with an accessible status instead so users aren’t presented with a blank screen while the snapshot loads.
- The `sessionStorage["bantayog.last-phone"]` key and access pattern are duplicated in `LoginPage` and `RegisterPage`; extracting this into a small shared helper (with the try/catch and default `+63`) would reduce drift and centralize the persistence logic.
## Individual Comments
### Comment 1
<location path="apps/citizen-pwa/public/sw.js" line_range="22-29" />
<code_context>
+ '/icons/icon-512.png',
+]
+
self.addEventListener('install', (event) => {
- self.skipWaiting()
+ event.waitUntil(
+ caches
+ .open(CACHE_NAME)
+ .then((cache) => cache.addAll(PRECACHE_URLS))
+ .then(() => self.skipWaiting()),
+ )
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Consider hardening install-time precaching so a single failure doesn’t block the entire service worker installation.
`cache.addAll(PRECACHE_URLS)` will reject the entire `install` if any resource fails (e.g. a transient failure fetching `manifest.webmanifest`), which prevents the new SW from ever activating. To avoid users being stuck on an old SW due to minor precache hiccups, consider handling failures (e.g. wrapping `addAll` in a `catch` and still calling `self.skipWaiting()`, or using `Promise.allSettled` with individual `cache.add` calls).
```suggestion
self.addEventListener('install', (event) => {
event.waitUntil(
caches
.open(CACHE_NAME)
.then((cache) =>
Promise.all(
PRECACHE_URLS.map((url) =>
cache.add(url).catch((error) => {
// Log and continue on individual precache failures so that
// a single bad asset does not block the entire SW install.
console.warn('[sw] Failed to precache', url, error)
}),
),
),
)
.catch((error) => {
// Even if opening the cache or the bulk precache logic fails,
// we still want the new service worker to activate.
console.error('[sw] Precache during install encountered an error', error)
})
.then(() => self.skipWaiting()),
)
})
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/citizen-pwa/src/components/SubmitReportForm/index.tsx`:
- Around line 56-72: The effect currently calls wizardSnapshot.load().then(...)
but lacks a .catch(), so if load() rejects or the then-handler throws,
setHasLoadedSnapshot(true) never runs and the wizard stays hidden; update the
promise chain on wizardSnapshot.load() to append a .catch(error => { if
(!cancelled) { process the error if needed (e.g., log) } }) and ensure
setHasLoadedSnapshot(true) is executed in a finally-like step (either by using
.finally(() => { if (!cancelled) setHasLoadedSnapshot(true) }) or by repeating
the flag set in both then and catch); keep existing uses of setStep and
setFormData inside the successful path (inside the then handler) and preserve
the cancelled guard.
In `@apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx`:
- Around line 315-318: The paragraph element in Step2WhoWhere (the <p> with
role="status") redundantly includes aria-live="polite"; remove the explicit
aria-live attribute from that element so it relies on the implicit
aria-live/aria-atomic behavior provided by role="status" to reduce noise and
keep accessibility semantics correct.
In `@apps/citizen-pwa/src/services/wizard-snapshot.test.ts`:
- Around line 55-65: The TTL test currently can pass falsely if the test
STORAGE_KEY drifts from production; update the test to exercise the TTL branch
by using the public API and fake timers: in the test for wizardSnapshot.load()
replace direct store writes to mockInstance._store.set(STORAGE_KEY, ...) with
wizardSnapshot.save(staleSnapshot) (use a WizardSnapshot object), call
vi.useFakeTimers({ now: Date.now() }) in beforeEach, advance time or call
vi.setSystemTime(Date.now() + 25 * 60 * 60 * 1000) to simulate >24h, then assert
wizardSnapshot.load() returns null and that the underlying mock store no longer
has the production key; restore timers with vi.useRealTimers() in afterEach.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 35ab3216-8ce5-40f3-81a7-e4d6fec8188e
📒 Files selected for processing (11)
apps/citizen-pwa/public/sw.jsapps/citizen-pwa/src/components/SubmitReportForm/Step1Evidence.tsxapps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsxapps/citizen-pwa/src/components/SubmitReportForm/index.tsxapps/citizen-pwa/src/pages/LoginPage.tsxapps/citizen-pwa/src/pages/RegisterPage.tsxapps/citizen-pwa/src/pages/SettingsPage.tsxapps/citizen-pwa/src/services/wizard-snapshot.test.tsapps/citizen-pwa/src/services/wizard-snapshot.tsdocs/learnings.mddocs/progress.md
…lience, a11y loading state, phone helper extraction, snapshot catch guard, TTL test fix
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/citizen-pwa/src/components/SubmitReportForm/index.tsx (1)
129-149:⚠️ Potential issue | 🟠 Major | ⚡ Quick winClear the wizard snapshot as soon as
createDraft()succeeds.Right now the snapshot survives until server confirmation. If the user refreshes from
queuedorfailed_retryable, the in-memorydraftis gone but the step-3 snapshot is still there, so reopening/reportcan create a second draft with a newclientDraftRefinstead of resuming the existing submission.♻️ Suggested lifecycle fix
const { draft: created, secret: draftSecret } = await createDraft({ reportType: formData.step1.reportType as ReportType, // barangayId holds the barangay name when selected; fall back to municipality label barangay: formData.step2.barangayId ?? formData.step2.municipalityLabel ?? '', @@ ...(photo ? { photo } : {}), }) + await wizardSnapshot.clear() setDraft(created) setSecret(draftSecret)Also applies to: 181-183
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/citizen-pwa/src/components/SubmitReportForm/index.tsx` around lines 129 - 149, After createDraft() succeeds and you call setDraft(created) and setSecret(draftSecret), immediately clear the persisted wizard snapshot (i.e., invoke whatever function or storage-removal you use to persist the step-3 wizard snapshot) so the UI can't later resume into a stale snapshot and accidentally create a second draft; do this right after setDraft/setSecret in the createDraft success block in SubmitReportForm (the same change must also be applied to the other creation branch around the second occurrence at the other block near the referenced lines). Ensure you clear the snapshot before any further navigation/confirmation logic and keep the clearing call tied to the createDraft success path so clientDraftRef remains authoritative.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/citizen-pwa/public/sw.js`:
- Around line 22-36: The install handler currently treats all PRECACHE_URLS as
optional and proceeds even if core shell files fail; update it to split assets
into REQUIRED_URLS (e.g., "/", "/index.html", core shell files) and
OPTIONAL_URLS (manifest, icons), open CACHE_NAME and call
cache.addAll(REQUIRED_URLS) so any failure rejects the install (thus keeping the
old worker), then individually cache.add(optional) with per-item catch for
best-effort; adjust the install flow inside the self.addEventListener('install',
...) block to reject the waitUntil promise on required failures and only swallow
errors for optional assets.
In `@apps/citizen-pwa/src/components/SubmitReportForm/index.tsx`:
- Around line 58-67: The restore logic only sets formData.step2 to snap.step2
but Step2WhoWhere still uses its own local state and never receives those
values; update the restore to fully hydrate all Step 2 fields (e.g., location,
contact, etc.) into formData by mapping snap.step2 properties into the same
shape Step2WhoWhere expects and ensure Step2WhoWhere receives those values as
initialValues (or refactor Step2WhoWhere to read from formData.step2 instead of
local state); specifically modify the wizardSnapshot.load() continuation that
calls setFormData and the Step2WhoWhere mount to pass/consume formData.step2 so
Step 2 is repopulated on reload or navigation.
In `@apps/citizen-pwa/src/pages/RegisterPage.tsx`:
- Around line 188-190: The phone input is being saved raw via setPhone and
setStoredPhone; update the input handler in RegisterPage (the onChange that sets
next, setPhone and setStoredPhone) to validate and normalize the value first:
trim whitespace, strip non-digit characters (or apply a limited allowed
pattern), enforce a max length (e.g. 15) and reject or truncate oversized
values, and only call setStoredPhone when the normalized value passes
validation; wrap the storage call inside a try/catch that logs errors instead of
swallowing them to handle sessionStorage failures. Ensure you modify the handler
where setPhone and setStoredPhone are called so the normalized value is used
consistently.
In `@apps/citizen-pwa/src/services/phone-session-storage.ts`:
- Around line 4-17: The current empty catch blocks in getStoredPhone and
setStoredPhone swallow all errors; change them to catch (e: unknown) and
distinguish known benign storage failures (e.g., DOMException names like
"QuotaExceededError", "NS_ERROR_DOM_QUOTA_REACHED", "SecurityError" or messages
containing "Quota" / "private" / "denied") and ignore those, but for any other
unexpected error call console.warn (or your module logger) with a clear message
including KEY and the error, then return DEFAULT_PHONE from getStoredPhone (and
silently return from setStoredPhone) so callers still get best-effort behavior
while unexpected failures are visible in logs.
In `@docs/progress.md`:
- Around line 60-65: Update the Round 1 note in the progress entry for Step 2 so
it matches the current markup in the Step2WhoWhere component: remove the
statement claiming the Step 2 hint uses role="status" and aria-live="polite"
(since that attribute was already removed and recorded on 2026-05-03) and
replace it with a brief note that the hint is no longer rendered with those ARIA
attributes, or delete the redundant sentence so the entry is not
self-contradictory.
---
Outside diff comments:
In `@apps/citizen-pwa/src/components/SubmitReportForm/index.tsx`:
- Around line 129-149: After createDraft() succeeds and you call
setDraft(created) and setSecret(draftSecret), immediately clear the persisted
wizard snapshot (i.e., invoke whatever function or storage-removal you use to
persist the step-3 wizard snapshot) so the UI can't later resume into a stale
snapshot and accidentally create a second draft; do this right after
setDraft/setSecret in the createDraft success block in SubmitReportForm (the
same change must also be applied to the other creation branch around the second
occurrence at the other block near the referenced lines). Ensure you clear the
snapshot before any further navigation/confirmation logic and keep the clearing
call tied to the createDraft success path so clientDraftRef remains
authoritative.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 8893a8aa-2ed2-4cac-b0f0-256e7c54d910
📒 Files selected for processing (9)
apps/citizen-pwa/public/sw.jsapps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsxapps/citizen-pwa/src/components/SubmitReportForm/index.tsxapps/citizen-pwa/src/pages/LoginPage.tsxapps/citizen-pwa/src/pages/RegisterPage.tsxapps/citizen-pwa/src/services/phone-session-storage.tsapps/citizen-pwa/src/services/wizard-snapshot.test.tsdocs/learnings.mddocs/progress.md
- SW: split precache into REQUIRED_URLS (/, /index.html) and OPTIONAL_URLS. Required failures reject install; optional failures are best-effort. - Wizard: clear snapshot immediately after createDraft() succeeds to prevent stale resumes creating duplicate drafts. - Step2WhoWhere: accept initialValues prop and hydrate from snapshot/formData. - RegisterPage: normalize phone input (strip non-digits, trim, 16-char max) before persisting to sessionStorage. - phone-session-storage: distinguish benign storage errors from unexpected ones; log unexpected errors instead of swallowing silently. - docs/progress.md: remove outdated aria-live mention from Step 2 entry. Verification: lint 0 errors, typecheck clean, 243/243 tests pass.
Summary
Two rounds of QA staging follow-ups bundled into a single PR. 9 issues from the round-1 (10-subagent) sweep + the round-2 auth/wizard sweep, scoped to citizen-pwa.
Round 1 (commit 013e8b5) — 4 issues, 3 source files:
/,/index.html, manifest, icons; navigation fallback to cached/index.html; cache versionv1→v2(apps/citizen-pwa/public/sw.js)text-[#768081]→text-surface-600, 1×text-[#a3adae]→text-surface-500(SettingsPage.tsx)<main id="main-content">landmark (same file)role="status"hint until a location method is picked (Step2WhoWhere.tsx)Round 2 (commit cdcf72b) — 5 real bugs, 6 files (4 modified + 2 new):
id/name/autoComplete="tel"+ real<label htmlFor>sessionStorage["bantayog.last-phone"]+useStatelazy initializerreportTypedefaults to''instead of seeded'flood';handleNextvalidates with inlinerole="alert"; type buttons clear the errorwizard-snapshotservice (localforage, 24h TTL, distinct fromdraft-store);WizardContainerloads on mount, saves on every step/formData change, clears on submit success and Step-1 back-to-home;Step1EvidenceacceptsinitialReportTypepropBooked, NOT in this PR (documented in
docs/progress.md)linkWithPhoneNumberrequirescurrentUserbut the codebase has nosignInAnonymouslyupstream. Fresh users hitting/registerdirectly get "Not signed in" with no recovery. Design decision needed (link vs. fresh sign-in vs. anon-then-link).Step2WhoWheremounts fresh on back navigation; needsinitialValuesprops.<main>landmark sweep for the other 9 non-shell pages (Register, Login, NotFound, Goodbye, Onboarding, wizard root, Lookup, Tracking, Receipt, IncidentDetail).90f29ef(two-stepreport_lookup); needs a staging redeploy to verify.OPERATION_NOT_ALLOWEDis a Firebase Console config (Authentication → Sign-in method), not code.Test plan
Local gate (already verified on the merge tip):
pnpm --filter @bantayog/citizen-pwa lint— 0 errors (14 pre-existing warnings auto-fixed by lint-staged)pnpm --filter @bantayog/citizen-pwa typecheck— cleannpx vitest run— 327/327 pass (54 files; +8 new wizard-snapshot tests)pnpm build— clean; SW + manifest + icons all indist/Manual verification (after staging redeploy):
/→ app shell renders (not the dinosaur)/settings→ score ≥ 95, no contrast violations on section headers / helper text/report→ Step 2 (no GPS/Manual picked yet) → "Pick a location method above…" hint visible/login→ enter+639XXXXXXXXX→ navigate/register→ confirm phone preserved/report→ tap "Skip photo for now" without picking a type → confirm "Please select an incident type to continue." inline error/report→ pick Flood → Continue → refresh → confirm wizard resumes at Step 2 with Flood retained on Back/report→ complete + submit → refresh after success →/reportstarts fresh (snapshot cleared)Rollback
🤖 Generated with Claude Code
Summary by Sourcery
Improve the citizen PWA’s offline behavior, accessibility, and report wizard resiliency, including auth phone preservation and mid-form snapshotting.
New Features:
Bug Fixes:
Enhancements:
Tests:
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Documentation
Accessibility